/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.servlet;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.lang.Files;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.ApiInitRequestMessage;
import cn.easyplatform.messages.request.ApiRequestMessage;
import cn.easyplatform.messages.request.BeginRequestMessage;
import cn.easyplatform.messages.request.SimpleRequestMessage;
import cn.easyplatform.messages.request.im.PagingRequestMessage;
import cn.easyplatform.messages.request.vfs.WriteRequestMessage;
import cn.easyplatform.messages.response.SimpleResponseMessage;
import cn.easyplatform.messages.vos.*;
import cn.easyplatform.messages.vos.h5.MsnVo;
import cn.easyplatform.messages.vos.h5.PagingVo;
import cn.easyplatform.messages.vos.h5.PushFieldVo;
import cn.easyplatform.messages.vos.h5.PushVo;
import cn.easyplatform.spi.service.ApiService;
import cn.easyplatform.spi.service.IdentityService;
import cn.easyplatform.spi.service.VfsService;
import cn.easyplatform.type.*;
import cn.easyplatform.web.WebApps;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.layout.IMainTaskBuilder;
import cn.easyplatform.web.layout.LayoutManagerFactory;
import cn.easyplatform.web.listener.SessionValidationScheduler;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.MainTaskSupport;
import cn.easyplatform.web.utils.ExtUtils;
import cn.easyplatform.web.utils.WebUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.action.*;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.util.resource.Labels;
import org.zkoss.web.servlet.http.Https;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.util.Composer;
import org.zkoss.zul.Div;
import org.zkoss.zul.Label;
import org.zkoss.zul.theme.Themes;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

import static cn.easyplatform.messages.vos.h5.MessageVo.TYPE_NOTICE;
import static cn.easyplatform.messages.vos.h5.MessageVo.TYPE_TASK;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
@WebServlet(name = "apisServlet", value = "/apis/*")
public class ApiGatewayServlet extends HttpServlet {

    private final static String CONTAINER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><?taglib uri=\"http://www.zkoss.org/dsp/web/core\" prefix=\"c\" ?><?link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"" + WebApps.getContextPath() + "/favicon.ico\"?><div zclass=\"w-100 h-100\" xmlns:n=\"native\" apply=\"" + PageController.class.getName() + "\"/>";

    private final static String MOBILE_CONTAINER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><?style type=\"text/css\" href=\"~./css/style-mobile.min.css?ver=202009151433\"?><?taglib uri=\"http://www.zkoss.org/dsp/web/core\" prefix=\"c\" ?><?meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"?><?link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"" + WebApps.getContextPath() + "/favicon.ico\"?><div zclass=\"w-100 h-100\" xmlns:n=\"native\" apply=\"" + PageController.class.getName() + "\"/>";

    public final static String API_APPENDER = "APIS";

    private final static String LOG_PATH = (String) WebApps.me().get("easyplatform.log");

    private static final LoggerContext CTX = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);

    private static final Configuration CONFIG = CTX.getConfiguration();

    private final static String ISSUER = "www.epclouds.com";

    private final static Logger LOG = LoggerFactory.getLogger(ApiGatewayServlet.class);

    private final static ObjectMapper mapper = new ObjectMapper();

    /**
     * 启动项目日志
     *
     * @param id 项目id
     */
    public final static void createLogAppender(String id) {
        if (CONFIG.getAppender(id + "-File") != null)
            return;

        StringBuilder sb = new StringBuilder();
        sb.append(LOG_PATH).append("/").append(id).append("/").append("apis.log");
        String fileName = sb.toString();
        sb.setLength(0);
        sb.append(LOG_PATH).append("/").append(id).append("/").append("apis-%d{yyyy-MM-dd}-%i.log.gz");
        String filePattern = sb.toString();
        final PatternLayout layout = PatternLayout.newBuilder()
                .withCharset(Charset.forName("UTF-8"))
                .withConfiguration(CONFIG)
                .withPattern("[%d{HH:mm:ss:SSS}] - %m%n")
                .build();
        final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder().withMax("30").withCustomActions(
                new Action[]{DeleteAction.createDeleteAction(FilenameUtils.normalize(LOG_PATH + "/" + id), false, 1, false, null, new PathCondition[]{IfFileName.createNameCondition("apis-*.log.gz", "apis-*.log.gz"), IfLastModified.createAgeCondition(Duration.parse("30d"))}, null, CONFIG)}).withConfig(CONFIG).build();
        final TriggeringPolicy policy = SizeBasedTriggeringPolicy.createPolicy("10 MB");
        final Appender appender = RollingRandomAccessFileAppender.newBuilder()
                .setName(id + "-File")
                .withImmediateFlush(true)
                .withFileName(fileName)
                .withFilePattern(filePattern)
                .setLayout(layout)
                .withPolicy(policy).withStrategy(strategy)
                .build();
        appender.start();
        CONFIG.addAppender(appender);
        final KeyValuePair[] pairs = {KeyValuePair.newBuilder().setKey(API_APPENDER).setValue(id).build()};
        final Filter filter = ThreadContextMapFilter.createFilter(pairs, null, Filter.Result.ACCEPT, Filter.Result.DENY);
        final LoggerConfig loggerConfig = CONFIG.getLoggerConfig(ApiGatewayServlet.class.getName());
        loggerConfig.addAppender(appender, loggerConfig.getLevel(), filter);
        CTX.updateLoggers(CONFIG);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String url = req.getRequestURI().substring(
                req.getContextPath().length());
        if (url.endsWith("/page")) {//直接请求页面
            String token = req.getHeader("token");
            if (Strings.isBlank(token))
                token = req.getParameter("token");
            if (!Strings.isBlank(token)) {
                String taskId = req.getParameter("tid");
                if (Strings.isBlank(taskId)) {
                    req.getSession().setAttribute("_resp_", Labels.getLabel("api.url.402"));
                    processZul(req, resp);
                } else {
                    try {
                        Jws<Claims> claims = Jwts.parser().setSigningKey(WebApps.me().getPublicKey()).parseClaimsJws(token);
                        ThreadContext.put(API_APPENDER, claims.getBody().getSubject());
                        Enumeration<String> ems = req.getParameterNames();
                        List<FieldVo> args = new ArrayList<>();
                        String source = req.getParameter("s");
                        TaskVo tv;
                        //来源于待办事项,id对应的是待办事项id
                        if (TYPE_TASK.equals(source)) {
                            if (LOG.isInfoEnabled())
                                LOG.info("doRoll request  {} {} {}", claims.getBody().getSubject(), claims.getBody().getAudience(), taskId);
                            long msgid = Nums.toLong(taskId, 0);
                            if (msgid <= 0) {
                                req.getSession().setAttribute("_resp_", Labels.getLabel("api.url.402"));
                                processZul(req, resp);
                                return;
                            }
                            MsnVo mv = new MsnVo();
                            mv.setMsgid(msgid);
                            mv.setType(TYPE_TASK);
                            ApiService ms = ServiceLocator.lookup(ApiService.class);
                            SimpleRequestMessage requestMessage = new SimpleRequestMessage(mv);
                            requestMessage.setSessionId(claims.getBody().getId());
                            IResponseMessage<?> rs = ms.poll(requestMessage);
                            if (rs.isSuccess()) {
                                if (LOG.isInfoEnabled())
                                    LOG.info("doRoll response  {} {} {}", claims.getBody().getSubject(), claims.getBody().getAudience(), rs.getBody());
                                PushVo pv = (PushVo) rs.getBody();
                                tv = new TaskVo(pv.getId());
                                tv.setProcessCode(pv.getCode());
                                FieldVo field = new FieldVo(FieldType.LONG);
                                field.setName("sys_msgid");
                                field.setValue(mv.getMsgid());
                                args.add(field);
                                if (pv.getFields() != null && !pv.getFields().isEmpty()) {
                                    for (PushFieldVo pf : pv.getFields()) {
                                        field = new FieldVo(FieldType.cast(pf.getValue()));
                                        field.setName(pf.getName());
                                        field.setValue(pf.getValue());
                                        args.add(field);
                                    }
                                }
                            } else {
                                if (LOG.isInfoEnabled())
                                    LOG.info("doRoll response {} {} {}:{}", claims.getBody().getSubject(), claims.getBody().getAudience(), rs.getCode(), rs.getBody());
                                req.getSession().setAttribute("_resp_", rs);
                                processZul(req, resp);
                                return;
                            }
                        } else {
                            while (ems.hasMoreElements()) {
                                String name = ems.nextElement();
                                if (!"tid".equals(name) && !"token".equals(name))
                                    args.add(new FieldVo(name, FieldType.VARCHAR, req.getParameter(name)));
                            }
                            tv = new TaskVo(taskId);
                        }
                        String device = ExtUtils.getDeviceType(req);
                        tv.setVariables(args);
                        tv.setAgentId(device);
                        tv.setGetEnv(req.getSession().getAttribute(Contexts.PLATFORM_APP_ENV) == null);
                        if (LOG.isInfoEnabled())
                            LOG.info("doPage request  {} {} {}", claims.getBody().getSubject(), claims.getBody().getAudience(), mapper.writeValueAsString(tv));
                        ApiService as = ServiceLocator
                                .lookup(ApiService.class);
                        BeginRequestMessage requestMessage = new BeginRequestMessage(tv);
                        requestMessage.setSessionId(claims.getBody().getId());
                        IResponseMessage<?> rs = as.task(requestMessage);
                        if (LOG.isInfoEnabled()) {
                            if (rs.isSuccess())
                                LOG.info("doPage response {} {} {}", claims.getBody().getSubject(), claims.getBody().getAudience(), mapper.writeValueAsString(rs.getBody()));
                            else
                                LOG.info("doPage response {} {} {}:{}", claims.getBody().getSubject(), claims.getBody().getAudience(), rs.getCode(), rs.getBody());
                        }
                        req.getSession().setAttribute("_resp_", rs);
                        req.getSession().setAttribute(Constants.SESSION_ID, claims.getBody().getId());
                        processZul(req, resp);
                    } catch (ExpiredJwtException e) {
                        req.getSession().setAttribute("_resp_", Labels.getLabel("api.token.expired"));
                        processZul(req, resp);
                    } catch (MalformedJwtException e) {
                        req.getSession().setAttribute("_resp_", Labels.getLabel("api.token.malformed"));
                        processZul(req, resp);
                    } catch (SignatureException e) {
                        req.getSession().setAttribute("_resp_", Labels.getLabel("api.token.signature"));
                        processZul(req, resp);
                    } finally {
                        ThreadContext.remove(API_APPENDER);
                    }
                }
            } else {
                req.getSession().setAttribute("_resp_", Labels.getLabel("api.url.401"));
                processZul(req, resp);
            }
        } else
            doPost(req, resp);
    }

    /**
     * 处理api接口
     *
     * @param req
     * @param resp
     * @param serviceId
     * @param apiId
     */
    private void doApi(HttpServletRequest req, HttpServletResponse resp, String serviceId, String apiId) throws IOException {
        ApiService as = ServiceLocator
                .lookup(ApiService.class);
        ApiCallVo av = new ApiCallVo();
        av.setAppId(serviceId);
        av.setApiId(apiId);
        String pageNo = req.getParameter("pageNo");
        String pageSize = req.getParameter("pageSize");
        String getTotal = req.getParameter("getTotal");
        if (!Strings.isBlank(pageNo))
            av.setPageNo(Integer.valueOf(pageNo));
        if (!Strings.isBlank(pageSize))
            av.setPageSize(Integer.valueOf(pageSize));
        if (!Strings.isBlank(getTotal))
            av.setGetTotal(getTotal.equalsIgnoreCase("true"));
        Enumeration<String> ems = req.getParameterNames();
        Map<String, Object> args = new HashMap<>();
        while (ems.hasMoreElements()) {
            String name = ems.nextElement();
            if (!"aid".equals(name) && !"appid".equals(name) && !"pageNo".equals(name) && !"pageSize".equals(name) && !"getTotal".equals(name))
                args.put(name, req.getParameter(name));
        }
        if (!Strings.isBlank(req.getContentType()) && req.getContentType().contains("application/json")) {
            Map<String, Object> data = begin("doApi", "", "", req.getInputStream(), java.util.Map.class);
            if (data != null)
                args.putAll(data);
        }
        av.setData(args);
        av.setIp(WebApps.getRemoteAddr(req));
        SimpleRequestMessage requestMessage = new SimpleRequestMessage(av);
        //requestMessage.setSessionId((String) req.getSession().getAttribute(Constants.SESSION_ID));
        IResponseMessage<?> rs = as.call(requestMessage);
        end("doApi", serviceId, apiId, resp, new TaskResp(rs));
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String url = req.getRequestURI().substring(
                req.getContextPath().length() + 5);
        if (!Strings.isBlank(req.getContentType()) && req.getContentType().contains("application/json")) {
            if (url.endsWith("/auth")) {//验证
                InitReq auth = begin("auth", "", "", req.getInputStream(), InitReq.class);
                if (Strings.isBlank(auth.getServiceId())) {
                    end("auth", auth.getServiceId(), auth.getNodeId(), resp, new TaskResp("E023", "The given serviceId must not be null!"));
                } else {
                    if (Strings.isBlank(auth.getType())) {
                        if (Strings.isBlank(auth.getOrgId())) {
                            end("auth", auth.getServiceId(), auth.getNodeId(), resp, new TaskResp("E023", "The given orgId must not be null!"));
                            return;
                        }
                        if (Strings.isBlank(auth.getNodeId())) {
                            end("auth", auth.getServiceId(), auth.getNodeId(), resp, new TaskResp("E023", "The given nodeId must not be null!"));
                            return;
                        }
                        if (Strings.isBlank(auth.getNodePass())) {
                            end("auth", auth.getServiceId(), auth.getNodeId(), resp, new TaskResp("E023", "The given nodePass must not be null!"));
                            return;
                        }
                    } else if (auth.getType().contains("wx") && Strings.isBlank(auth.getCode())) {//小程序验证登陆
                        end("auth", auth.getServiceId(), auth.getCode(), resp, new TaskResp("E023", "The given code must not be null!"));
                        return;
                    }
                    //创建appender
                    createLogAppender(auth.getServiceId());
                    ThreadContext.put(API_APPENDER, auth.getServiceId());
                    try {
                        ApiInitVo av = new ApiInitVo();
                        av.setNodeIp(WebApps.getRemoteAddr(req));
                        av.setOrgId(auth.getOrgId());
                        av.setServiceId(auth.getServiceId());
                        av.setUserId(auth.getNodeId());
                        av.setUserPass(auth.getNodePass());
                        av.setType(auth.getType());
                        if (auth.getType() != null && auth.getType().contains("wx"))
                            av.setUserId(auth.getCode());
                        av.setDeviceType(DeviceType.MOBILE.getName());
                        if (LOG.isInfoEnabled())
                            LOG.info("doAuth request  {} {} {}", av.getServiceId(), av.getUserId(), mapper.writeValueAsString(av));
                        ApiService as = ServiceLocator
                                .lookup(ApiService.class);
                        IResponseMessage<?> rp = as.auth(new ApiInitRequestMessage(av));
                        if (rp.isSuccess()) {
                            Map<String, Object> map = (Map<String, Object>) rp.getBody();
                            Number timeout = (Number) map.get("timeout");
                            Serializable sessionKey = (Serializable) map.remove("sessionKey");
                            Date now = new Date();
                            String token = Jwts.builder().setIssuer(ISSUER).setAudience(av.getUserId()).setSubject(av.getServiceId()).setIssuedAt(now).setExpiration(DateUtils.addSeconds(now, timeout.intValue())).signWith(WebApps.me().getPrivateKey(), SignatureAlgorithm.RS512).setId(sessionKey.toString()).compact();
                            map.put("token", token);
                        }
                        end("Auth", auth.getServiceId(), auth.getNodeId(), resp, new TaskResp(rp));
                    } finally {
                        ThreadContext.remove(API_APPENDER);
                    }
                }
            } else {
                String token = req.getHeader("token");
                if (Strings.isBlank(token)) {
                    if (url.endsWith("/do")) {//处理api
                        String serviceId = req.getParameter("appid");
                        String apiId = req.getParameter("aid");
                        if (!Strings.isBlank(serviceId) && !Strings.isBlank(apiId)) {//执行匿名api
                            doApi(req, resp, serviceId, apiId);
                        } else
                            resp.sendError(401);
                    } else
                        resp.sendError(401);
                } else {
                    try {
                        Jws<Claims> claims = Jwts.parser().setSigningKey(WebApps.me().getPublicKey()).parseClaimsJws(token);
                        ThreadContext.put(API_APPENDER, claims.getBody().getSubject());
                        if (url.endsWith("/do")) {//请求调用api
                            TaskReq task = begin("Task", claims.getBody().getSubject(), claims.getBody().getAudience(), req.getInputStream(), TaskReq.class);
                            if (Strings.isBlank(task.getId())) {
                                end("Task", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp("E023", "The given id must not be null!"));
                            } else {
                                ApiVo av = new ApiVo();
                                av.setId(task.getId());
                                av.setUser(task.getUser());
                                if (!Strings.isBlank(task.getRole()))
                                    av.setRole(task.getRole());
                                av.setData(task.getData());
                                if (task.getStartNo() != null && task.getStartNo() > 0)
                                    av.setStartNo(task.getStartNo());
                                if (task.getPageNo() != null && task.getPageNo() > 0)
                                    av.setStartNo(task.getPageNo());
                                av.setPageSize(task.getPageSize());
                                av.setGetTotal(task.getGetTotal());
                                if (!Strings.isBlank(task.getProcessCode()))
                                    av.setProcessCode(task.getProcessCode());
                                ApiService as = ServiceLocator
                                        .lookup(ApiService.class);
                                ApiRequestMessage begin = new ApiRequestMessage(av);
                                begin.setSessionId(claims.getBody().getId());
                                end("Task", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp(as.api(begin)));
                            }
                        } else if (url.endsWith("/token")) {//请求获取token
                            if (LOG.isInfoEnabled())
                                LOG.info("doToken request  {} ", token);
                            ApiService as = ServiceLocator
                                    .lookup(ApiService.class);
                            SimpleRequestMessage request = new SimpleRequestMessage(token);
                            request.setSessionId(claims.getBody().getId());
                            IResponseMessage<?> rp = as.token(request);
                            if (rp.isSuccess()) {
                                Number timeout = (Number) rp.getBody();
                                Date now = new Date();
                                rp = new SimpleResponseMessage(Jwts.builder().setIssuer(ISSUER).setAudience(claims.getBody().getAudience()).setSubject(claims.getBody().getSubject()).setIssuedAt(now).setExpiration(DateUtils.addSeconds(now, timeout.intValue())).signWith(WebApps.me().getPrivateKey(), SignatureAlgorithm.RS512).setId(claims.getBody().getId()).compact());
                            }
                            end("Token", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp(rp));
                        } else if (url.startsWith("/message")) {//消息
                            if (LOG.isInfoEnabled())
                                LOG.info("doMessage request  {} ", token);
                            if (url.endsWith("/paging")) {
                                Map<String, Object> data = begin("Message paging", claims.getBody().getSubject(), claims.getBody().getAudience(), req.getInputStream(), Map.class);
                                int pageSize = Nums.toInt(data.get("pageSize"), 0), pageNo = Nums.toInt(data.get("pageNo"), 0);
                                Object messageType = data.get("messageType"), getTotal = data.get("getTotal"), status = data.get("status");
                                if (pageSize == 0) {
                                    end("Message paging", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp("E023", "The given pageSize must not be null!"));
                                } else if (pageNo == 0) {
                                    end("Message paging", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp("E023", "The given pageNo must not be null!"));
                                } else if (messageType == null) {
                                    end("Message paging", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp("E023", "The given messageType must not be null!"));
                                } else {
                                    PagingVo pv = new PagingVo();
                                    pv.setPageNo(pageNo);
                                    pv.setPageSize(pageSize);
                                    pv.setType(messageType.toString());
                                    pv.setGetTotal(getTotal == null ? false : getTotal.toString().equalsIgnoreCase("true"));
                                    if (status != null)
                                        pv.setStatus(Nums.toInt(status, -1));
                                    ApiService ms = ServiceLocator.lookup(ApiService.class);
                                    PagingRequestMessage request = new PagingRequestMessage(pv);
                                    request.setSessionId(claims.getBody().getId());
                                    end("Message paging", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp(ms.paging(request)));
                                }
                            } else if (url.endsWith("/content")) {
                                Map<String, Object> data = begin("Message content", claims.getBody().getSubject(), claims.getBody().getAudience(), req.getInputStream(), Map.class);
                                long id = Nums.toLong(data.get("id"), 0);
                                if (id == 0)
                                    end("Message content", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp("E023", "The given id must not be null!"));
                                else {
                                    MsnVo mv = new MsnVo();
                                    mv.setMsgid(id);
                                    mv.setType(TYPE_NOTICE);
                                    ApiService ms = ServiceLocator.lookup(ApiService.class);
                                    SimpleRequestMessage requestMessage = new SimpleRequestMessage(mv);
                                    requestMessage.setSessionId(claims.getBody().getId());
                                    end("Message content", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp(ms.poll(requestMessage)));
                                }
                            } else
                                resp.sendError(404);
                        } else if (url.endsWith("/exit")) {//退出
                            if (LOG.isInfoEnabled())
                                LOG.info("doExit request  {} ", token);
                            IdentityService as = ServiceLocator
                                    .lookup(IdentityService.class);
                            SimpleRequestMessage request = new SimpleRequestMessage();
                            request.setSessionId(claims.getBody().getId());
                            end("Exit", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp(as.logout(request)));
                            req.getSession().invalidate();
                        } else {
                            resp.sendError(404);
                        }
                    } catch (ExpiredJwtException e) {
                        end(url, "", "", resp, new TaskResp("E001", Labels.getLabel("api.token.expired")));
                    } catch (MalformedJwtException e) {
                        end(url, "", "", resp, new TaskResp("E002", Labels.getLabel("api.token.malformed")));
                    } catch (SignatureException e) {
                        end(url, "", "", resp, new TaskResp("E003", Labels.getLabel("api.token.signature")));
                    } finally {
                        ThreadContext.remove(API_APPENDER);
                    }
                }
            }
        } else if (url.endsWith("/do")) {
            String serviceId = req.getParameter("appid");
            String apiId = req.getParameter("aid");
            if (!Strings.isBlank(serviceId) && !Strings.isBlank(apiId)) {//执行匿名api
                doApi(req, resp, serviceId, apiId);
            } else
                resp.sendError(401);
        } else if (url.endsWith("/file/get")) {
            getFile(req, resp);
        } else if (url.endsWith("/file/put")) {
            putFile(req, resp);
        } else if (url.endsWith("/exit")) {
            Jws<Claims> claims = null;
            String token = req.getParameter("token");
            if (token == null)
                token = req.getHeader("token");
            if (!Strings.isBlank(token)) {
                try {
                    claims = Jwts.parser().setSigningKey(WebApps.me().getPublicKey()).parseClaimsJws(token);
                    if (LOG.isInfoEnabled())
                        LOG.info("doExit request  {} ", token);
                    IdentityService as = ServiceLocator
                            .lookup(IdentityService.class);
                    SimpleRequestMessage request = new SimpleRequestMessage();
                    request.setSessionId(claims.getBody().getId());
                    end("Exit", claims.getBody().getSubject(), claims.getBody().getAudience(), resp, new TaskResp(as.logout(request)));
                    req.getSession().invalidate();
                } catch (Exception e) {
                    resp.sendError(401);
                }
            } else {
                resp.sendError(401);
            }
        } else resp.sendError(400);
    }

    /**
     * 处理资源文件
     *
     * @param req
     * @param resp
     */
    private void putFile(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String path = req.getHeader("filePath");
        if (Strings.isBlank(path) || Strings.isBlank(req.getContentType())) {
            resp.sendError(404);
            return;
        }
        Jws<Claims> claims = null;
        String token = req.getParameter("token");
        if (token == null)
            token = req.getHeader("token");
        if (!Strings.isBlank(token)) {
            try {
                claims = Jwts.parser().setSigningKey(WebApps.me().getPublicKey()).parseClaimsJws(token);
            } catch (Exception e) {
                resp.sendError(401);
                return;
            }
        } else {
            resp.sendError(401);
            return;
        }
        if (req.getContentType().contains("form")) {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setHeaderEncoding("UTF-8");
            String name = req.getHeader("fileName");
            try {
                List<FileItem> list = upload.parseRequest(req);
                VfsService vs = ServiceLocator.lookup(VfsService.class);
                for (FileItem item : list) {
                    if (!item.isFormField() && !Strings.isBlank(item.getName())) {
                        VfsVo vo = new VfsVo();
                        vo.setPath(path + File.separatorChar + (Strings.isBlank(name) ? item.getName() : name));
                        vo.setName(Strings.isBlank(name) ? item.getName() : name);
                        vo.setData(IOUtils.toByteArray(item.getInputStream()));
                        WriteRequestMessage rm = new WriteRequestMessage(null, vo);
                        rm.setSessionId(claims.getBody().getId());
                        IResponseMessage<?> rs = vs.write(rm);
                        if (!rs.isSuccess())
                            resp.sendError(500, (String) rs.getBody());
                    }
                }
            } catch (FileUploadException e) {
                resp.sendError(500, "Upload error");
            }

        } else {
            String name = req.getHeader("fileName");
            if (Strings.isBlank(name)) {
                resp.sendError(404);
                return;
            }
            byte[] data = IOUtils.toByteArray(req.getInputStream());
            if (path.startsWith("$7")) {
                VfsService vs = ServiceLocator.lookup(VfsService.class);
                VfsVo vo = new VfsVo();
                vo.setPath(path + File.separatorChar + name);
                vo.setName(name);
                vo.setData(data);
                WriteRequestMessage rm = new WriteRequestMessage(null, vo);
                rm.setSessionId(claims.getBody().getId());
                IResponseMessage<?> rs = vs.write(rm);
                if (!rs.isSuccess())
                    resp.sendError(500, (String) rs.getBody());
            } else {
                File file = null;
                if (path.startsWith("/"))
                    file = new File(WebApps.getRealPath(path + File.separatorChar + name));
                else
                    file = new File(FilenameUtils.normalize(WebApps.getRealPath("apps/" + claims.getBody().getSubject() + "/" + path + "/" + name)));
                Files.write(file, data);
            }
        }
    }

    /**
     * 处理资源文件
     *
     * @param req
     * @param resp
     */
    private void getFile(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String name = req.getParameter("id");
        if (Strings.isBlank(name)) {
            resp.sendError(404);
            return;
        }
        byte[] data = null;
        if (name.startsWith("$708") || !name.startsWith("$7")) {
            String app = req.getParameter("appid");
            if (Strings.isBlank(app)) {
                String token = req.getParameter("token");
                if (token == null)
                    token = req.getHeader("token");
                if (Strings.isBlank(token)) {
                    resp.sendError(401);
                    return;
                }
                Jws<Claims> claims = null;
                try {
                    claims = Jwts.parser().setSigningKey(WebApps.me().getPublicKey()).parseClaimsJws(token);
                } catch (Exception e) {
                    resp.sendError(401);
                    return;
                }
                app = claims.getBody().getSubject();
            }
            EnvVo env = new EnvVo();
            env.setProjectId(app);
            data = WebUtils.getFileContent(env, null, name);
        } else {
            Jws<Claims> claims = null;
            String token = req.getParameter("token");
            if (token == null)
                token = req.getHeader("token");
            if (!Strings.isBlank(token)) {
                try {
                    claims = Jwts.parser().setSigningKey(WebApps.me().getPublicKey()).parseClaimsJws(token);
                } catch (Exception e) {
                    resp.sendError(401);
                    return;
                }
            } else {
                resp.sendError(401);
                return;
            }

            IdentityService as = ServiceLocator.lookup(IdentityService.class);
            SimpleRequestMessage rm = new SimpleRequestMessage();
            rm.setSessionId(claims.getBody().getId());
            IResponseMessage<?> rs = as.getAppEnv(rm);
            if (!rs.isSuccess()) {
                resp.sendError(500, (String) rs.getBody());
                return;
            }
            EnvVo env = (EnvVo) rs.getBody();
            data = WebUtils.getFileContent(env, claims.getBody().getAudience(), name);
        }
        if (data == null)
            resp.sendError(404);
        else {
            resp.setContentType("application/octet-stream;charset=utf-8");
            resp.addHeader("Pragma", "No-cache");
            resp.addHeader("Cache-Control", "No-cache");
            resp.setDateHeader("Expires", 0);
            resp.addHeader("content-type", "application/octet-stream");
            resp.setHeader("Content-Disposition", "attachment; filename=" + FilenameUtils.getName(name));
            OutputStream os = resp.getOutputStream();
            if (data.length > 200) {
                byte[] bs = Https.gzip(req, resp, null, data);
                if (bs != null)
                    data = bs; //yes, browser support compress
            }
            resp.setContentLength(data.length);
            os.write(data);
            resp.flushBuffer();
            os.close();
        }
    }

    private <T> T begin(String name, String projectId, String userId, InputStream is, Class<T> clazz) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "utf-8"));
        try {
            StringBuilder json = new StringBuilder();
            String line = br.readLine();
            while (line != null) {
                json.append(line);
                line = br.readLine();
            }
            String content = json.toString();
            if (LOG.isInfoEnabled())
                LOG.info("do{} request  {} {} {}", name, projectId, userId, content);
            br.close();
            if (Strings.isBlank(content) || !content.startsWith("{") || !content.endsWith("}"))
                return null;
            return mapper.readValue(content, clazz);
        } finally {
            br.close();
        }
    }

    private void end(String name, String projectId, String userId, HttpServletResponse response, TaskResp resp) {
        PrintWriter printWriter = null;
        try {
            String content = mapper.writeValueAsString(resp);
            if (LOG.isInfoEnabled())
                LOG.info("do{} response {} {} {}", name, projectId, userId, content);
            response.addHeader("Content-Type", "application/json;charset=utf-8");
            printWriter = response.getWriter();
            printWriter.write(content);
            printWriter.flush();
        } catch (Exception e) {
            if (LOG.isErrorEnabled())
                LOG.error("do{} response {} {}", name, projectId, userId, e);
            try {
                response.sendError(500);
            } catch (Exception ex) {
            }
        } finally {
            IOUtils.closeQuietly(printWriter);
        }
    }

    public static void processZul(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (DeviceType.MOBILE.getName().equals(ExtUtils.getDeviceType(req)))
            WebUtils.processZul(req, resp, MOBILE_CONTAINER);
        else
            WebUtils.processZul(req, resp, CONTAINER);
    }

    public static class PageController implements Composer<Component> {
        @Override
        public void doAfterCompose(Component component) throws Exception {
            Desktop desktop = component.getDesktop();
            Object obj = desktop.getSession().removeAttribute("_resp_");
            if (obj instanceof IResponseMessage) {
                IResponseMessage resp = (IResponseMessage) obj;
                if (resp.isSuccess()) {
                    Object data;
                    if (resp.getBody() instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) resp.getBody();
                        data = map.get("result");
                        EnvVo env = (EnvVo) map.get("env");
                        desktop.getSession().setAttribute(Contexts.PLATFORM_APP_ENV, env);
                        desktop.getSession().setAttribute(Constants.SESSION_ID, env.getSessionId());
                        if (!Strings.isBlank(env.getTheme()) && !Themes.getCurrentTheme().equals(env.getTheme()))
                            Themes.setTheme(Executions.getCurrent(), env.getTheme());
                    } else {
                        EnvVo env = (EnvVo) desktop.getSession().getAttribute(Contexts.PLATFORM_APP_ENV);
                        if (env != null && !Strings.isBlank(env.getTheme()) && !Themes.getCurrentTheme().equals(env.getTheme()))
                            Themes.setTheme(Executions.getCurrent(), env.getTheme());
                        data = resp.getBody();
                    }
                    if (data instanceof AbstractPageVo) {
                        MainTaskSupport builder = null;
                        AbstractPageVo pv = (AbstractPageVo) data;
                        try {
                            builder = (MainTaskSupport) LayoutManagerFactory
                                    .createLayoutManager()
                                    .getMainTaskBuilder(component,
                                            pv.getId(), pv);
                            desktop.setAttribute(Contexts.ANONYMOUS_TASKS, new ArrayList<>());
                            ((IMainTaskBuilder) builder).build();
                            if (WebApps.me().isSessionKeepAlive() && component.getPage().getXelVariable("$easyplatform") == null) //发送心跳
                                SessionValidationScheduler.start(component);
                            WebUtils.setAnonymousInfo(desktop, pv.getId());
                            EnvVo env = (EnvVo) desktop.getSession().getAttribute(Contexts.PLATFORM_APP_ENV);
                            component.getPage().setTitle(env.getTitle() + "-" + builder.getName());
                        } catch (EasyPlatformWithLabelKeyException ex) {
                            if (builder != null)
                                builder.close(false);
                            component.appendChild(createPanel(Labels.getLabel(ex.getMessage(), ex.getArgs())));
                        } catch (BackendException ex) {
                            if (builder != null)
                                builder.close(false);
                            component.appendChild(createPanel(ex.getMsg().getCode() + ":" + ex.getMsg().getBody()));
                        } catch (Exception ex) {
                            if (LOG.isErrorEnabled())
                                LOG.error("doTask", ex);
                            if (builder != null)
                                builder.close(false);
                            component.appendChild(createPanel(ex.getMessage()));
                        }
                    }
                } else {
                    component.appendChild(createPanel(resp.getCode() + ":" + resp.getBody()));
                }
            } else {
                component.appendChild(createPanel(obj.toString()));
            }
        }

        private Component createPanel(String message) {
            Div div = new Div();
            div.setStyle("margin-top:10em");
            div.setZclass("mx-auto text-center");
            Label label = new Label(message);
            label.setZclass("text-danger font-weight-bold text-break");
            label.setParent(div);
            return div;
        }
    }

    static class InitReq {
        private String serviceId;
        private String orgId;
        private String code;
        private String type;
        private String nodeId;
        private String nodePass;

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getServiceId() {
            return serviceId;
        }

        public void setServiceId(String serviceId) {
            this.serviceId = serviceId;
        }

        public String getOrgId() {
            return orgId;
        }

        public void setOrgId(String orgId) {
            this.orgId = orgId;
        }

        public String getNodeId() {
            return nodeId;
        }

        public void setNodeId(String nodeId) {
            this.nodeId = nodeId;
        }

        public String getNodePass() {
            return nodePass;
        }

        public void setNodePass(String nodePass) {
            this.nodePass = nodePass;
        }
    }

    static class TaskReq {
        private String token;
        private String id;
        private String user;
        private String role;
        private Boolean getTotal;
        private Integer startNo;
        private Integer pageNo;
        private Integer pageSize;
        private String processCode;

        public Integer getPageNo() {
            return pageNo;
        }

        public void setPageNo(Integer pageNo) {
            this.pageNo = pageNo;
        }

        public Boolean getGetTotal() {
            return getTotal;
        }

        public void setGetTotal(Boolean getTotal) {
            this.getTotal = getTotal;
        }

        public String getUser() {
            return user;
        }

        public void setUser(String user) {
            this.user = user;
        }

        public Integer getStartNo() {
            return startNo;
        }

        public void setStartNo(Integer startNo) {
            this.startNo = startNo;
        }

        public Integer getPageSize() {
            return pageSize;
        }

        public void setPageSize(Integer pageSize) {
            this.pageSize = pageSize;
        }

        private Map<String, Object> data;

        public String getToken() {
            return token;
        }

        public void setToken(String token) {
            this.token = token;
        }

        public Map<String, Object> getData() {
            return data;
        }

        public void setData(Map<String, Object> data) {
            this.data = data;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getRole() {
            return role;
        }

        public void setRole(String role) {
            this.role = role;
        }

        public String getProcessCode() {
            return processCode;
        }

        public void setProcessCode(String processCode) {
            this.processCode = processCode;
        }
    }

    static class TaskResp {
        private String code;
        private Object data;

        public TaskResp(IResponseMessage<?> resp) {
            this.code = resp.getCode();
            this.data = resp.getBody();
        }

        public TaskResp(String code, Object data) {
            this.code = code;
            this.data = data;
        }

        public String getCode() {
            return code;
        }

        public Object getData() {
            return data;
        }
    }
}
